package com.demo.modules.test.user.utils;

import com.alibaba.fastjson.JSON;
import com.demo.common.constants.DistributedLockKeyConstants;
import com.demo.common.redis.service.RedisService;
import com.demo.common.utils.DateUtils;
import com.demo.common.utils.DistributedLockToolUtils;
import com.demo.modules.test.user.mapper.CommonMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * sharding  分库分表策略配置：按照年月
 *
 * @author molong
 * @since 2024/6/26
 */
@Slf4j
public class DateShardingAlgorithm implements PreciseShardingAlgorithm<LocalDateTime>, RangeShardingAlgorithm<LocalDateTime> {
    private final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMM");
    private static RedisService redisService;
    private static CommonMapper commonMapper;

    public static void setRedisService(RedisService redisService) {
        DateShardingAlgorithm.redisService = redisService;
    }

    public static void setCommonMapper(CommonMapper commonMapper) {
        DateShardingAlgorithm.commonMapper = commonMapper;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDateTime> psv) {
        //逻辑表名称
        final String logicTable = psv.getLogicTableName();
        //添加日期,真实的分表名称 logicTable_yyyyMM
        final String realTable = logicTable + "_" + psv.getValue().format(this.format);
        boolean exist = shardingTablesExistsCheck(realTable);

        //匹配到了
        if (exist) {
            if (log.isDebugEnabled()) {
                log.debug("分表更新: {} 是否存在:{}", realTable, exist);
            }
            return realTable;
        }
        //建表逻辑加锁
        DistributedLockToolUtils lock=DistributedLockToolUtils.build(DistributedLockKeyConstants.SHARDING_TABLE_KEY+realTable);
        try{
            lock.lock();
            //再次判断
            exist = shardingTablesExistsCheck(realTable);
            if (log.isDebugEnabled()) {
                log.debug("分表更新: {} 是否存在:{}", realTable, exist);
            }
            //匹配到了
            if (exist) {
                return realTable;
            }
            //未匹配到
            if (log.isDebugEnabled()) {
                log.debug("sharding 未匹配到表，需要创建");
            }

            // 缓存中无此表 建表 并添加缓存
            String sql = "CREATE TABLE IF NOT EXISTS " + realTable + " LIKE " + logicTable;
            commonMapper.executeSql(sql);
            redisService.sAdd(RandomUserUtil.TABLE_NAME_CACHE, realTable);

            return realTable;
        }finally {
            lock.unlock();
        }

    }

    /**
     * 范围匹配
     */
    @SneakyThrows
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<LocalDateTime> rsv) {
        final String table = rsv.getLogicTableName();
        final String prefix = table + "_";
        if (log.isDebugEnabled()) {
            log.debug("分表查询 表:{}", table);
        }

        //计算出时间范围内的所有日期
        Range<LocalDateTime> range = rsv.getValueRange();
        LocalDateTime lowerDate = range.lowerEndpoint();
        LocalDateTime upperDate = range.upperEndpoint();
        List<String> validTables=new ArrayList<>();
        for (LocalDate dateTime : DateUtils.listSubDate(lowerDate.toLocalDate(), upperDate.toLocalDate())) {
            String resultTableName = prefix+ dateTime.format(this.format);
            if (shardingTablesExistsCheck(resultTableName)) {
                validTables.add(resultTableName);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("查询表:{}", JSON.toJSONString(validTables));
        }
        if (CollectionUtils.isEmpty(validTables)) {
            if (log.isDebugEnabled()) {
                log.debug("查询表不存在 改查原始表");
            }
            return Lists.newArrayList(table);
        }
        return validTables;
    }
    /**
     * 判断表是否存在于缓存中
     *
     * @param resultTableName 表名
     * @return 是否存在于缓存中
     */
    public boolean shardingTablesExistsCheck(String resultTableName) {
        return redisService.sIsMember(RandomUserUtil.TABLE_NAME_CACHE, resultTableName);
    }
}